package lol.clann.minecraft.springboot.plugin.bukkit.impl;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import lol.clann.minecraft.springboot.api.bukkit.model.EventType;
import lol.clann.minecraft.springboot.api.bukkit.utils.EventUtils;
import lol.clann.minecraft.springboot.api.bukkit.utils.PluginUtils;
import lol.clann.minecraft.springboot.api.context.SpringContext;
import lol.clann.minecraft.springboot.api.model.core.ClassScanner;
import lol.clann.minecraft.springboot.api.model.core.SBPConfig;
import lombok.extern.slf4j.Slf4j;
import org.bukkit.event.Event;
import org.bukkit.event.EventPriority;
import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener;
import org.bukkit.plugin.EventExecutor;
import org.bukkit.plugin.RegisteredListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.type.filter.AssignableTypeFilter;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 *
 * @author pyz
 * @date 2019/5/21 11:09 PM
 */
@Slf4j
@Service
public class EventUtilsImpl implements EventUtils {

    private Map<String, EventType> eventTypeNameMap = new HashMap<>();
    private Map<Class, EventType> eventTypeClassMap = new HashMap<>();
    private List<EventType> eventTypes = new ArrayList<>();

    @Autowired
    private PluginUtils pluginUtils;
    @Autowired
    private SBPConfig config;

    @PostConstruct
    private void init() {
        ClassScanner scanner = new ClassScanner(SpringContext.getApplicationEnvironment());
        scanner.addIncludeFilter(new AssignableTypeFilter(Event.class));
        List<String> eventClasses = new ArrayList<>(scanner.doScan("org.bukkit.event"));
        eventClasses.sort(String::compareTo);
        for (String eventClassName : eventClasses) {
            try {
                Class eventClass = Class.forName(eventClassName);
                HandlerList handlerList = findHandlerList(eventClass);
                if (handlerList != null) {
                    EventType eventType = EventType.builder()
                            .name(eventClass.getSimpleName())
                            .type(eventClass)
                            .handlerList(handlerList)
                            .build();
                    eventTypes.add(eventType);
                    eventTypeClassMap.put(eventType.getType(), eventType);
                    eventTypeNameMap.put(eventType.getName(), eventType);
                    if (config.isDebugEventTypeDetect()) {
                        LOGGER.info("find event classes:" + eventType.getType().getName());
                    }
                }
            } catch (Throwable e) {
                continue;
            }
        }
        eventTypeNameMap = ImmutableMap.copyOf(eventTypeNameMap);
        eventTypeClassMap = ImmutableMap.copyOf(eventTypeClassMap);
        eventTypes = ImmutableList.copyOf(eventTypes);
    }

    private HandlerList findHandlerList(Class eventClass) {
        if (Modifier.isAbstract(eventClass.getModifiers())) {
            return null;
        }
        while (eventClass != null) {
            try {
                Method getHandlerList = eventClass.getDeclaredMethod("getHandlerList", new Class[0]);
                getHandlerList.setAccessible(true);
                return (HandlerList) getHandlerList.invoke(null);
            } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
                // ignore
                eventClass = eventClass.getSuperclass();
            }
        }
        return null;
    }

    @Override
    public Map<String, EventType> getEventTypeNameMap() {
        return eventTypeNameMap;
    }

    @Override
    public Map<Class, EventType> getEventTypeClassMap() {
        return eventTypeClassMap;
    }

    @Override
    public List<EventType> getEventTypes() {
        return eventTypes;
    }

    @Override
    public RegisteredListener registerListener(Class<? extends Event> eventClass, Listener handle, EventPriority priority, boolean ignoreCancelled, EventExecutor executor) {
        EventType eventType = eventTypeClassMap.get(eventClass);
        if (eventType == null) {
            throw new IllegalArgumentException(eventClass.getName());
        }
        RegisteredListener registeredListener = new RegisteredListener(handle, executor, priority, pluginUtils.getSelf(), ignoreCancelled);
        eventType.getHandlerList().register(registeredListener);
        return registeredListener;
    }

    @Override
    public void unregister(Class<? extends Event> eventClass, RegisteredListener listener) {
        EventType eventType = eventTypeClassMap.get(eventClass);
        if (eventType == null) {
            return;
        }
        eventType.getHandlerList().unregister(listener);
    }

}
